package com.moli.lumpSugar.websocket.service.impl;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.json.JSONUtil;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.moli.lumpSugar.api.client.user.UserClient;
import com.moli.lumpSugar.api.domain.entity.role.RoleEnum;
import com.moli.lumpSugar.common.cache.user.UserCache;
import com.moli.lumpSugar.common.config.ThreadPoolConfig;
import com.moli.lumpSugar.common.domain.dto.WSBaseResp;
import com.moli.lumpSugar.transaction.service.PushService;
import com.moli.lumpSugar.websocket.domain.dto.WSChannelExtraDto;
import com.moli.lumpSugar.websocket.service.adapter.WSAdapter;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import me.chanjar.weixin.mp.api.WxMpService;
import me.chanjar.weixin.mp.bean.result.WxMpQrCodeTicket;
import com.moli.lumpSugar.api.domain.entity.user.User;
import com.moli.lumpSugar.websocket.service.LoginService;
import com.moli.lumpSugar.websocket.service.WebSocketService;
import com.moli.lumpSugar.websocket.util.NettyUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Component;

import java.time.Duration;
import java.util.Date;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;

/**
 * @program: LumpSugarChat
 * @description:
 * @author: zhangjt
 * @create: 2024-08-23 16:03
 **/
@Slf4j
@Component
public class WebSocketServiceImpl implements WebSocketService {
    private static final Duration EXPIRE_TIME = Duration.ofHours(1);
    private static final Long MAX_MUM_SIZE = 10000L;
    /*
     * 管理所有用户的连接，包括用户态和游客
     * */
    private static final ConcurrentHashMap<Channel, WSChannelExtraDto> ONLINE_CHANNEL = new ConcurrentHashMap<>();
    /**
     * 所有在线的用户和对应的socket
     */
    private static final ConcurrentHashMap<Long, CopyOnWriteArrayList<Channel>> ONLINE_UID_MAP = new ConcurrentHashMap<>();
    /**
     * 所有请求登录的code与channel关系
     */
    public static final Cache<Integer, Channel> WAIT_LOGIN_MAP = Caffeine.newBuilder()
            .expireAfterWrite(EXPIRE_TIME)
            .maximumSize(MAX_MUM_SIZE)
            .build();
    /**
     * redis保存loginCode的key
     */
    private static final String LOGIN_CODE = "loginCode";

    @Autowired
    private WxMpService wxMpService;
    @Autowired
    private LoginService loginService;
    @Autowired
    private ApplicationEventPublisher applicationEventPublisher;
    @Autowired
    private ThreadPoolConfig threadPoolConfig;
    @Autowired
    private UserCache userCache;
    @Autowired
    private UserClient userClient;
    @Autowired
    private PushService pushService;

    /**
     * 处理用户登录请求，需要返回一张带code的二维码
     *
     * @param channel
     */
    @SneakyThrows
    @Override
    public void handleLoginReq(Channel channel) {
        //生成随机不重复的登录码,并将channel存在本地cache中
        Integer code = generateLoginCode(channel);
        //请求微信接口，获取登录码地址
        WxMpQrCodeTicket wxMpQrCodeTicket = wxMpService.getQrcodeService().qrCodeCreateTmpTicket(code, (int) EXPIRE_TIME.getSeconds());
        //返回给前端（channel必在本地）
        sendMsg(channel, WSAdapter.buildLoginResp(wxMpQrCodeTicket));
    }

    @Override
    public void connect(ChannelHandlerContext ctx) {
        ONLINE_CHANNEL.put(ctx.channel(), new WSChannelExtraDto());
    }

    @Override
    public void userOffLine(Channel channel) {
//        WSChannelExtraDto wsChannelExtraDTO = ONLINE_CHANNEL.get(channel);
//        Optional<Long> uidOptional = Optional.ofNullable(wsChannelExtraDTO)
//                .map(WSChannelExtraDto::getUid);
//        boolean offlineAll = offline(channel, uidOptional);
//        if (uidOptional.isPresent() && offlineAll) {
//            //已登录用户断连,并且全下线成功
//            User user = new User();
//            user.setId(uidOptional.get());
//            user.setLastOptTime(new Date());
//            applicationEventPublisher.publishEvent(new UserOfflineEvent(this, user));
//        }
    }
    /**
     * 用户下线
     * return 是否全下线成功
     */
    private boolean offline(Channel channel, Optional<Long> uidOptional) {
        ONLINE_CHANNEL.remove(channel);
        if (uidOptional.isPresent()) {
            CopyOnWriteArrayList<Channel> channels = ONLINE_UID_MAP.get(uidOptional.get());
            if (CollectionUtil.isNotEmpty(channels)) {
                channels.removeIf(ch -> Objects.equals(ch, channel));
            }
            return CollectionUtil.isEmpty(ONLINE_UID_MAP.get(uidOptional.get()));
        }
        return true;
    }

    @Override
    public void scanLoginSuccess(Integer code, Long uid) {
        Channel userLoginChannel = WAIT_LOGIN_MAP.getIfPresent(code);
        if (Objects.isNull(userLoginChannel)) {
            return;
        }
        User user = userClient.getUser(uid);
//        User user = userDao.getById(uid);
        // 调用登录模块获取token
        String token = loginService.login(user.getId());
        // 移除code
        WAIT_LOGIN_MAP.invalidate(code);
        loginSuccess(userLoginChannel,token,user);
    }

    @Override
    public void scanSuccess(Integer code) {
        Channel ifPresent = WAIT_LOGIN_MAP.getIfPresent(code);
        if (Objects.isNull(ifPresent)) {
            return;
        }
        sendMsg(ifPresent, WSAdapter.buildScanSuccessResp());
    }

    @Override
    public void authorizeReq(Channel channel, String token) {
        Long validUid = loginService.getValidUid(token);
        if (Objects.isNull(validUid)) {
            sendMsg(channel, WSAdapter.buildInvalidateTokenResp());
            return;
        }
        User user = userClient.getUser(validUid);
        loginSuccess(channel,token,user);
    }

    @Override
    public void sendAllOnlineUser(WSBaseResp<?> buildBlackUserMsg) {
        threadPoolConfig.websocketExecutor().execute(()->{
            for (Channel ch : ONLINE_CHANNEL.keySet()) {
                sendMsg(ch, buildBlackUserMsg);
            }
        });
    }

    /**
     * 获取不重复的登录的code，微信要求最大不超过int的存储极限
     * 防止并发，可以给方法加上synchronize，也可以使用cas乐观锁
     *
     * @return
     */
    private Integer generateLoginCode(Channel channel) {
        int inc;
        do {
            //本地cache时间必须比redis key过期时间短，否则会出现并发问题
            // 使用一个随机数，值在integer的最大值范围内
            inc = (int) (Math.random() * Integer.MAX_VALUE);
//            inc = RedisUtils.integerInc(RedisKey.getKey(LOGIN_CODE), (int) EXPIRE_TIME.toMinutes(), TimeUnit.MINUTES);
        } while (Objects.nonNull(WAIT_LOGIN_MAP.asMap().putIfAbsent(inc, channel)));
        return inc;
    }

    /**
     * 给本地channel发送消息
     *
     * @param channel
     * @param wsBaseResp
     */
    private void sendMsg(Channel channel, WSBaseResp<?> wsBaseResp) {
        channel.writeAndFlush(new TextWebSocketFrame(JSONUtil.toJsonStr(wsBaseResp)));
    }

    /**
     * (channel必在本地)登录成功，并更新状态
     */
    private void loginSuccess(Channel channel,String token,User user) {
        //更新上线列表
        online(channel, user.getId());
        //返回给用户登录成功
        Boolean hasPower = (Boolean) userClient.hasPower(user.getId(), RoleEnum.CHAT_MANAGER);
        // 登录成功，返回给前端
        sendMsg(channel, WSAdapter.buildLoginSuccessResp(user,token,hasPower));
        //发送用户上线事件
        boolean online = userCache.isOnline(user.getId());
        if (!online) {
            user.setLastOptTime(new Date());
            user.refreshIp(NettyUtil.getAttribute(channel, NettyUtil.IP));
            pushService.sendPushLoginMsg(WSAdapter.buildLoginSuccessMQMsg(user));
//            pushService.sendPushMsg(WSAdapter.buildOnlineNotifyResp(user));
        }
    }
    /**
     * 用户上线
     */
    private void online(Channel channel, Long uid) {
        getOrInitChannelExt(channel).setUid(uid);
        ONLINE_UID_MAP.putIfAbsent(uid, new CopyOnWriteArrayList<>());
        ONLINE_UID_MAP.get(uid).add(channel);
        NettyUtil.setAttribute(channel, NettyUtil.UID, uid);
    }
    /**
     * 如果在线列表不存在，就先把该channel放进在线列表
     *
     * @param channel
     * @return
     */
    private WSChannelExtraDto getOrInitChannelExt(Channel channel) {
        WSChannelExtraDto wsChannelExtraDTO =
                ONLINE_CHANNEL.getOrDefault(channel, new WSChannelExtraDto());
        WSChannelExtraDto old = ONLINE_CHANNEL.putIfAbsent(channel, wsChannelExtraDTO);
        return ObjectUtil.isNull(old) ? wsChannelExtraDTO : old;
    }

    @Override
    public void sendToUid(WSBaseResp<?> wsBaseResp, Long uid) {
        CopyOnWriteArrayList<Channel> channels = ONLINE_UID_MAP.get(uid);
        if (CollectionUtil.isEmpty(channels)) {
            log.info("用户：{}不在线", uid);
            return;
        }
        channels.forEach(channel -> {
            threadPoolConfig.websocketExecutor().execute(() -> sendMsg(channel, wsBaseResp));
        });
    }
}
